// ----------------------------------------------------------------------------
// thread_impl.apple.h
// ----------------------------------------------------------------------------
/*
 Description:
 pthreadによる基本スレッドクラスの実装. 
 */
#ifndef FW_THREAD_IMPL_APPLE_H
#define FW_THREAD_IMPL_APPLE_H
#include <fw/base/core/stdinteger.h>
#include <fw/base/concurrent/mutex.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/mach.h>
#include <algorithm>

namespace fw
{
	int Impl_GetNumberOfProcessors()
	{
		int numCPU=0;
		int mib[4];
		size_t len = sizeof(numCPU);
		
		mib[0] = CTL_HW;
		mib[1] = HW_AVAILCPU;
		
		sysctl(mib, 2, &numCPU, &len, NULL, 0);
		
		if(numCPU < 1)
		{
			mib[1] = HW_NCPU;
			sysctl(mib, 2, &numCPU, &len, NULL, 0);
			if(numCPU < 1 )
			{
				numCPU = 1;
			}
		}
		
		return numCPU;
	}
		
	int Impl_ConvertPriority(Thread::Priority priority)
	{
		int result = 32;
		switch(priority)
		{
			case Thread::PRIO_LOWEST:
				result = 16;
				break;
			case Thread::PRIO_LOW:
				result = 24;
				break;
			case Thread::PRIO_NORMAL:
				result = 32;
				break;
			case Thread::PRIO_HIGH:
				result = 40;
				break;
			case Thread::PRIO_HIGHEST:
				result = 46;
				break;
			default:
				FW_ASSERT(0,L"想定されていないスレッド優先順位です.\n");
				break;
			
		}
		return result;
	}
	
	void Impl_SetPthreadPriority(pthread_t pthread, Thread::Priority prio) /*pthread_attr_t& attr,*/
	{
		const int policy = SCHED_OTHER;
		int osPriority = Impl_ConvertPriority(prio);
		int schedMin = sched_get_priority_min( policy );
		int schedMax = sched_get_priority_max( policy );
		osPriority = std::min(std::max(osPriority, schedMin), schedMax);
		
		struct sched_param param;
		memset(&param, 0, sizeof(sched_param));
		param.sched_priority = osPriority;
		
		//int ret = pthread_attr_setschedparam(&attr, &param);
		//FW_ASSERT(ret==0, "Failed to set schedparam.[%d]\n", ret);
		int ret = pthread_setschedparam(pthread, policy, &param);
		FW_ASSERT(ret==0, "Failed to set schedparam.[%d]\n", ret);
		FW_UNUSED(ret);
	}
	
	int Impl_GetPthreadPriority(pthread_t pthread)
	{
		if(pthread==0)
		{
			return 0;
		}

		int policy;
		sched_param param;		
		pthread_getschedparam(pthread, &policy, &param);
		
		return param.sched_priority;
	}

	integer_t Impl_ConvertAffinity(Thread::Affinity affi)
	{
		int a=static_cast<int>(affi);
		integer_t mask=THREAD_AFFINITY_TAG_NULL;
		for(int i=0; i<Impl_GetNumberOfProcessors(); ++i)
		{
			if(a&(1<<i))
			{
				mask |= static_cast<integer_t>(1<<i);
			}
		}
		return mask;
	}
	
	void Impl_SetPthreadAffinity(pthread_t pthread, Thread::Affinity affi)
	{
		if(pthread==0)
		{
			return;
		}
		//integer_t am = Impl_ConvertAffinity(affi);
		//FW_PRINTF("am=0x%08x\n", am);
	}
	
	int Impl_GetPthreadAffinity(pthread_t pthread)
	{
		if(pthread==0)
		{
			return 0;
		}
		thread_policy_t			old_threadPolicy;
		mach_msg_type_number_t	old_threadPolicyCountFilled;
		boolean_t				getDefault;
		
		old_threadPolicy = (thread_policy_t)malloc(sizeof(thread_affinity_policy_t));
		old_threadPolicyCountFilled = THREAD_AFFINITY_POLICY_COUNT;
		
		kern_return_t ret = thread_policy_get(pthread_mach_thread_np(pthread),
											  THREAD_AFFINITY_POLICY,
											  old_threadPolicy,
											  &old_threadPolicyCountFilled,
											  &getDefault);
		int result = 0;
		if(!ret)
		{
			integer_t affinityTag = *(integer_t*)old_threadPolicy;
			if(affinityTag==THREAD_AFFINITY_TAG_NULL)
			{	// default affinity
				result = (1<<Impl_GetNumberOfProcessors())-1;
			}
			else
			{
				result = affinityTag;
			}
		}
		free(old_threadPolicy);
		return result;
	}
	
	class ThreadImpl
	{
	private:
		
		pthread_t						m_threadid;
		
		ThreadFunc						m_threadfunc;
		
		ThreadArg						m_argument;
		
		const char*						m_name;
		
		bool							m_joinable;
		
		Mutex							m_mutex;
		
		static FW_THREAD_LS ThreadImpl*	s_thread;
		
		static void*					ThreadProxy(void* param)
		{
			FW_ASSERT( param != 0 );
			
			ThreadImpl* pthreadbody = reinterpret_cast<ThreadImpl*>(param);
			
			FW_ASSERT(ThreadImpl::s_thread==0);
			ThreadImpl::s_thread = pthreadbody;
			
			(*pthreadbody)();
			
			FW_ASSERT(ThreadImpl::s_thread!=0);
			ThreadImpl::s_thread = 0;
			
			return reinterpret_cast<void*>(0);
		}
		
		void							operator()()
		{
			MutexLocker locker(m_mutex);
			FW_ASSERT(m_threadfunc);
			(*m_threadfunc)(m_argument);
		}
		
	public:
		
		 ThreadImpl()
		: m_threadid  (0)
		, m_threadfunc(0)
		, m_argument  (0)
		, m_name      (0)
		, m_joinable  (false)
		, m_mutex	  ("threadmtx")
		{
		}
		
		~ThreadImpl()
		{
			if(m_threadid && m_threadfunc)
			{
				pthread_detach(m_threadid);
				m_threadid = 0;
			}
		}
		
		void							Attach(const char* name, uint32_t affinity)
		{
			m_threadid = pthread_self();
			m_threadfunc = 0;
			m_argument = 0;
			m_name = name;
			m_joinable = false;
		}
		
		void							Initialize(const char*		name,
												   ThreadFunc		function,
												   ThreadArg		argument,
												   Thread::Affinity	affinity,
												   Thread::Priority	priority,
												   size_t			stackSize)
		{
			m_threadfunc = function;
			m_argument   = argument;
			
			// TODO : implement affinity. 
			MutexLocker locker(m_mutex);
			
			pthread_attr_t attr;
			int status;
			status=pthread_attr_init(&attr);
			FW_ASSERT(status==0, "Failed to initialize pthread_attr_init.\n");
			status=pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
			FW_ASSERT(status==0, "Failed to initialize pthread_attr_setdetachstate.\n");
			
			int setStackSize = PTHREAD_STACK_MIN + static_cast<int>(stackSize);
			status=pthread_attr_setstacksize(&attr, setStackSize);
			FW_ASSERT(status==0, "Failed to initialize pthread_attr_setstacksize.\n");			

			status=pthread_create(&m_threadid, &attr, ThreadImpl::ThreadProxy, this);
			FW_ASSERT(status==0, "Failed to create thread.[%d]\n", status);
			
			Impl_SetPthreadPriority(m_threadid, priority);			
			Impl_SetPthreadAffinity(m_threadid, affinity);
			 
			//int prio = Impl_GetPthreadPriority(m_threadid);
			//int affi = Impl_GetPthreadAffinity(m_threadid);
			//FW_PRINTF("**** prio = %d affi = 0x%02x\n", prio, affi);
						
			m_joinable = true;
		}
		
		void							Join()
		{
			FW_ASSERT(m_threadid, "Failed to join thread.\n");
			FW_ASSERT(m_joinable, "This thread is not joinable.\n");
			int status = pthread_join(m_threadid, 0);
			FW_ASSERT(status==0, "pthread_join failure[%d].\n", status);
			FW_UNUSED(status);
		}
		
	}; // class ThreadImpl 
	
	FW_THREAD_LS ThreadImpl*	ThreadImpl::s_thread = 0;

} // namespace fw 

#endif // FW_THREAD_IMPL_APPLE_H 
